In [311]:
import pandas as pd
import numpy as np
import yfinance as yf
import random
from matplotlib import pyplot as plt
import seaborn as sns
import warnings
import secrets
import plotly as plotly
import plotly.express as px
from tqdm import tqdm
import plotly.graph_objects as go
pd.set_option('plotting.backend', 'matplotlib')
plotly.offline.init_notebook_mode()
#warnings.simplefilter(action='ignore', category=FutureWarning
import plotly.io as pio
plotly_template = pio.templates["plotly_dark"]
plt.style.use('dark_background')
#sas
PORTFOLIOS
In [312]:
TICKERS=["SPY","FTSEMIB.MI","ITSC.MI"]
NUMBER_OF_SIMULATIONS=1000
STARTING_CAPITAL = 10000
YEARS_OF_SIMULATION=50
In [ ]:
%%sql
In [314]:
YEARS_OF_SIMULATION=YEARS_OF_SIMULATION+1
Market_Days=253
df_Tickers={x: yf.download(x)["Adj Close"].pct_change(1).dropna() for x in TICKERS}
[*********************100%%**********************] 1 of 1 completed [*********************100%%**********************] 1 of 1 completed [*********************100%%**********************] 1 of 1 completed
In [315]:
df_simulations={x: np.zeros((YEARS_OF_SIMULATION,NUMBER_OF_SIMULATIONS)) for x in TICKERS}
for TICKER in TICKERS:
df_local=df_simulations[TICKER]
df_local[0, :] = STARTING_CAPITAL
df_simulations[TICKER]=df_local
for x in tqdm(range(0,NUMBER_OF_SIMULATIONS)):
_=np.array((df_Tickers[TICKER].iloc[:]))
for i in range(1,YEARS_OF_SIMULATION):
sample=np.random.choice(_,Market_Days)+1
annual_change=np.prod(sample)
df_simulations[TICKER][i,x]=annual_change*df_simulations[TICKER][i-1,x]
df_simulations={x: pd.DataFrame(df_simulations[x]) for x in TICKERS}
df_simulations_1= df_simulations
100%|██████████| 1000/1000 [00:01<00:00, 817.59it/s] 100%|██████████| 1000/1000 [00:01<00:00, 794.19it/s] 100%|██████████| 1000/1000 [00:01<00:00, 767.94it/s]
In [315]:
In [316]:
quantile={x: list(df_simulations[x].iloc[YEARS_OF_SIMULATION-1].quantile([0.1,0.9])) for x in TICKERS}
print(quantile)
df_simulations_purged={x: 40 for x in TICKERS}
print(df_simulations_purged)
for TICKER in TICKERS:
df_local=df_simulations[TICKER]
df_simulations_purged[TICKER]=df_local[df_local.columns[ df_local.max() < quantile[TICKER][1]]]
df_simulations_purged[TICKER]=df_simulations_purged[TICKER][df_simulations_purged[TICKER].columns[ df_simulations_purged[TICKER].max() > quantile[TICKER][0]]]
{'SPY': [216510.04428352288, 6699057.775320291], 'FTSEMIB.MI': [1906.3978623624946, 141157.62034518042], 'ITSC.MI': [2987.1184223866517, 119661.80524388277]}
{'SPY': 40, 'FTSEMIB.MI': 40, 'ITSC.MI': 40}
In [317]:
for TICKER in TICKERS:
print(df_simulations_purged[TICKER].shape)
(51, 808) (51, 848) (51, 859)
In [318]:
#for TICKER in TICKERS:
# df_simulations_purged[TICKER].plot(figsize=(16,8), title=f"Simulation of {TICKER} {NUMBER_OF_SIMULATIONS} portfolios", legend=False)
In [319]:
for TICKER in TICKERS:
df_simulations_purged[TICKER].iloc[YEARS_OF_SIMULATION-1].plot.density(figsize=(16,8),fontsize=14, xlim=(-10000,900000), label=TICKER , legend= True)
In [320]:
df_statistics= {x: 0 for x in TICKERS}
for TICKER in TICKERS:
top_25 =[]
low_25 =[]
median=[]
for i in range(0,YEARS_OF_SIMULATION):
top_25.append(df_simulations[TICKER].iloc[i].quantile(0.75))
low_25.append(df_simulations[TICKER].iloc[i].quantile(0.25))
median.append(df_simulations[TICKER].iloc[i].median())
columns=["top 25%","median","bottom 25%"]
df_statistics[TICKER]=pd.DataFrame(list(zip(top_25,median,low_25)),columns=columns)
In [321]:
#ax= df_statistics.plot(legend=None,logy=False,fontsize=20,figsize=(25,15),linewidth=4,color="black",title=f"Simulation of {NUMBER_OF_SIMULATIONS} portfolios")
pd.set_option('plotting.backend', 'plotly')
for TICKER in TICKERS:
fig = df_statistics[TICKER].plot(width=1600, height=800,title=f"Simulation of {NUMBER_OF_SIMULATIONS} {TICKER} portfolios",labels= {"index": "Years ", "value":"Total Capital"} ,template='plotly_dark')
fig.add_trace(go.Scatter(x=list(range(0,YEARS_OF_SIMULATION)),y=np.array(df_statistics[TICKER]["top 25%"]),fill='tonexty',mode='lines', line_color='blue' , fillcolor ="red", showlegend=False, hoverinfo="skip"))
fig.add_trace(go.Scatter(x=list(range(0,YEARS_OF_SIMULATION)),y=np.array(df_statistics[TICKER]["median"]),fill='tonexty',mode='lines', line_color='orange', fillcolor="green", showlegend=False, hoverinfo="skip"))
fig.show()
In [322]:
df_simulations_at_profit={x:0 for x in TICKERS}
for TICKER in TICKERS:
ls_simulations_at_profit=[]
for i in range(YEARS_OF_SIMULATION):
c=len([1 for i in list(df_simulations[TICKER].iloc[i])if i > STARTING_CAPITAL])
ls_simulations_at_profit.append(c/NUMBER_OF_SIMULATIONS*100)
pd.set_option('plotting.backend', 'plotly')
df_simulations_at_profit[TICKER] = pd.DataFrame(ls_simulations_at_profit,columns=["Profit %"])
fig=df_simulations_at_profit[TICKER].plot(title=f"Minimum investment horizon of {TICKER}", labels= {"index": "Period length in years", "value":"Chance to be in profit"},template='plotly_dark')
fig.show()
In [323]:
for TICKER in TICKERS:
print(f"The median return of {TICKER} is {round((((np.median(np.array(df_simulations[TICKER].iloc[YEARS_OF_SIMULATION-1,:]))/STARTING_CAPITAL)**(1/YEARS_OF_SIMULATION))-1)*100,2)}%")
The median return of SPY is 9.74% The median return of FTSEMIB.MI is 0.91% The median return of ITSC.MI is 1.05%
In [324]:
def drawdown(a):
a= a.pct_change().dropna()
acc_max = np.maximum.accumulate(a)
return (a - acc_max).min()
median_Drawdown={x : [] for x in TICKERS}
for TICKER in TICKERS:
for s in range(NUMBER_OF_SIMULATIONS-1):
series=df_simulations[TICKER].iloc[:,s]
d=drawdown(series)
median_Drawdown[TICKER].append(d)
print(f"The median drawdown of {TICKER} is {round(np.median(median_Drawdown[TICKER])*100,2)}%")
The median drawdown of SPY is -90.11% The median drawdown of FTSEMIB.MI is -107.97% The median drawdown of ITSC.MI is -101.48%
In [324]:
In [325]:
#TICKERS=["AAPL", "DB"]
df_joined=yf.download(TICKERS)["Adj Close"].pct_change(1).dropna()
df_joined.corr()
[*********************100%%**********************] 3 of 3 completed
C:\Users\dadoi\AppData\Local\Temp\ipykernel_20460\867487608.py:2: FutureWarning: The default fill_method='pad' in DataFrame.pct_change is deprecated and will be removed in a future version. Call ffill before calling pct_change to retain current behavior and silence this warning.
Out[325]:
| FTSEMIB.MI | ITSC.MI | SPY | |
|---|---|---|---|
| FTSEMIB.MI | 1.000000 | 0.454097 | 0.551700 |
| ITSC.MI | 0.454097 | 1.000000 | 0.244392 |
| SPY | 0.551700 | 0.244392 | 1.000000 |
In [326]:
rows=[]
for TICKER in TICKERS:
for TICKER_1 in TICKERS:
Mak_Score=round(np.median((np.array(df_joined[TICKER_1][(df_joined[TICKER_1]>0) & (df_joined[TICKER]>0) ])+1e-10)/(np.array(df_joined[TICKER][(df_joined[TICKER_1]>0) & (df_joined[TICKER]>0) ])+1e-10)),2) - round(np.median((np.array(df_joined[TICKER_1][(df_joined[TICKER_1]<0) & (df_joined[TICKER]<0) ])+1e-10)/(np.array(df_joined[TICKER][(df_joined[TICKER_1]<0) & (df_joined[TICKER]<0) ])+1e-10)))
rows.append([TICKER,TICKER_1,Mak_Score])
df_Mak=pd.DataFrame(data=rows, columns=["tick1","tick2","Mak coefficent"])
df_Mak
Out[326]:
| tick1 | tick2 | Mak coefficent | |
|---|---|---|---|
| 0 | SPY | SPY | 0.00 |
| 1 | SPY | FTSEMIB.MI | 0.34 |
| 2 | SPY | ITSC.MI | -0.13 |
| 3 | FTSEMIB.MI | SPY | -0.25 |
| 4 | FTSEMIB.MI | FTSEMIB.MI | 0.00 |
| 5 | FTSEMIB.MI | ITSC.MI | -0.37 |
| 6 | ITSC.MI | SPY | 0.14 |
| 7 | ITSC.MI | FTSEMIB.MI | -0.42 |
| 8 | ITSC.MI | ITSC.MI | 0.00 |
/
PORTOFLIO OPTIMISATION
In [327]:
df_simulations_yr_={x: np.zeros((YEARS_OF_SIMULATION,len(TICKERS))) for x in TICKERS}
df_joined=yf.download(TICKERS)["Adj Close"].pct_change(1).dropna()
df_joined=df_joined[TICKERS]
df_joined_3=df_joined
f_matrix= df_joined.to_numpy()
f_matrix
[*********************100%%**********************] 3 of 3 completed
C:\Users\dadoi\AppData\Local\Temp\ipykernel_20460\840832971.py:2: FutureWarning: The default fill_method='pad' in DataFrame.pct_change is deprecated and will be removed in a future version. Call ffill before calling pct_change to retain current behavior and silence this warning.
Out[327]:
array([[ 0.03218813, 0.04007147, 0.01354742],
[ 0.00307504, 0.00372188, 0.01045435],
[ 0.01762429, 0.01071676, 0.01022937],
...,
[ 0.00109316, 0.00867773, 0. ],
[ 0.00543889, -0.00593315, 0. ],
[-0.001588 , 0.00733902, 0.48598109]])
In [328]:
#maxe a list of al possible combinations without repetition of all natural numbers from 0 to 10 where the sum of the components is always 10
import numpy as np
def all_possible_combinations_Mak(n,k):
# Create a 3D grid of all possible combinations of [i, j, k]
grid = np.indices((n + 1 for _ in range(k)))
# Sum along the last axis to check if i + j + k == n
sum_grid = grid.sum(axis=0)
# Find indices where the sum equals n
valid_indices = np.argwhere(sum_grid == n)
valid_indices=np.divide(valid_indices,n)
return valid_indices
i=10
q=10000
a=all_possible_combinations_Mak(i,len(TICKERS))
while True:
if len(a)<q:
a=all_possible_combinations_Mak(i,len(TICKERS))
i=round(i*(np.log2(q/len(a))))
else:
break
In [329]:
i=0
return_matrix=np.zeros((len(f_matrix),len(a)))
for row in tqdm(a):
j=0
for pair in f_matrix:
#return_matrix[j,i]=np.sum([pair[x]*row[x] for x in range(0,len(TICKERS))])
return_matrix[j,i]=np.dot(pair,row)
j=j+1
i=i+1
100%|██████████| 10878/10878 [01:24<00:00, 129.15it/s]
In [330]:
plane_of_returns=[]
i=0
j=0
Rf=yf.download("^TNX")["Adj Close"].dropna().iloc[-1]*0.01
print(Rf)
#return_matrix=np.flip(return_matrix)
for x in return_matrix.T:
median=np.median(x)*Market_Days
values=a[i]
mean=np.mean(x)*Market_Days
std=np.std(x)*Market_Days
sharpe_ratio=(mean-Rf)/std
plane_of_returns.append(np.append(values ,[median ,mean,std,sharpe_ratio]))
i=i+1
plane_of_returns=np.array(plane_of_returns)
df_plane=pd.DataFrame(plane_of_returns,columns=TICKERS+["Median","Mean","Std","Sharpe_ratio"])
df_plane
[*********************100%%**********************] 1 of 1 completed 0.04147000312805176
Out[330]:
| SPY | FTSEMIB.MI | ITSC.MI | Median | Mean | Std | Sharpe_ratio | |
|---|---|---|---|---|---|---|---|
| 0 | 0.000000 | 0.000000 | 1.000000 | 0.000000 | 0.019055 | 2.652497 | -0.008451 |
| 1 | 0.000000 | 0.006849 | 0.993151 | 0.004012 | 0.019192 | 2.645945 | -0.008420 |
| 2 | 0.000000 | 0.013699 | 0.986301 | 0.007939 | 0.019330 | 2.639587 | -0.008388 |
| 3 | 0.000000 | 0.020548 | 0.979452 | 0.011717 | 0.019468 | 2.633424 | -0.008355 |
| 4 | 0.000000 | 0.027397 | 0.972603 | 0.015623 | 0.019606 | 2.627458 | -0.008321 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 10873 | 0.986301 | 0.006849 | 0.006849 | 0.139688 | 0.114147 | 2.942929 | 0.024695 |
| 10874 | 0.986301 | 0.013699 | 0.000000 | 0.140439 | 0.114284 | 2.952608 | 0.024661 |
| 10875 | 0.993151 | 0.000000 | 0.006849 | 0.134882 | 0.114668 | 2.949116 | 0.024820 |
| 10876 | 0.993151 | 0.006849 | 0.000000 | 0.140891 | 0.114806 | 2.958693 | 0.024787 |
| 10877 | 1.000000 | 0.000000 | 0.000000 | 0.134472 | 0.115328 | 2.964931 | 0.024910 |
10878 rows × 7 columns
Sharpe Ratio=σp Rp−Rf
In [331]:
hover=""
i=0
for TICKER in TICKERS:
if i %2 == 0:
hover=hover+'<b>'+TICKER+': %{customdata['+str(i)+']:.4f}</b>'
else:
hover=hover+'<br><b>'+TICKER+': %{customdata['+str(i)+']:.4f}</b></br>'
i=i+1
hover=hover+'<br><b>Standard Deviation: %{x:.3f}</b></br> '+ '<b>Mean: %{y:.3f}</b>'+ '<br><b>Sharpe Ratio %{text:.4f}</b></br>'
fig=go.Figure()
fig.add_trace(go.Scatter(x=df_plane["Std"], y=df_plane["Mean"], mode='markers',marker=dict(size=10,color=df_plane["Sharpe_ratio"],colorscale='Viridis',showscale=True),hovertemplate =hover
,customdata=np.stack((df_plane[TICKER] for TICKER in TICKERS), axis=-1),text=df_plane["Sharpe_ratio"]))
fig.update_layout(width=1600, height=800)
fig.update_layout(
hoverlabel=dict(
bgcolor="white",
font_size=16
),
)
fig.show()
C:\Users\dadoi\anaconda3\Lib\site-packages\IPython\core\interactiveshell.py:3466: FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
In [331]:
In [331]:
In [331]:
In [331]:
In [ ]:
!jupyter nbconvert --to html Portfolio1.ipynb --HTMLExporter.theme=dark
In [ ]: